package com.jlf.core.engine.server.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jlf.core.constant.DomainConstant;
import com.jlf.core.engine.constant.SchemaConstant;
import com.jlf.core.engine.domain.SchemaIndexDO;
import com.jlf.core.engine.entity.MetaTable;
import com.jlf.core.engine.entity.field.DeleteFieldWrapper;
import com.jlf.core.engine.entity.field.EditFieldWrapper;
import com.jlf.core.engine.meta.MetaRepository;
import com.jlf.core.engine.persistence.SchemaDomainPersistence;
import com.jlf.core.engine.persistence.SchemaFieldPersistence;
import com.jlf.core.engine.domain.SchemaFieldDO;
import com.jlf.core.engine.query.SchemaFieldQueryInput;
import com.jlf.core.engine.server.SchemaFieldServer;
import com.jlf.core.error.Errors;
import com.jlf.core.output.ListResultWrapper;
import com.jlf.core.output.ResultException;
import com.jlf.tool.id.SnowflakeId;
import com.jlf.tool.toolkit.StringToolkit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 领域字段的服务实现
 *
 * @author wujr
 * 2023/7/7
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/7 1.0 新增]
 */
@Slf4j
@RequiredArgsConstructor
public class SchemaFieldServerImpl implements SchemaFieldServer {
    private final SchemaFieldPersistence schemaFieldPersistence;
    private final SchemaDomainPersistence schemaDomainPersistence;
    private final MetaRepository metaRepository;

    @Override
    public SchemaFieldDO add(SchemaFieldDO record) throws Exception {
        if (StringUtils.isBlank(record.getJdbcFieldCode())){
            record.setJdbcFieldCode(StringToolkit.camel2Underline(record.getClassFieldCode()));
        }
        if (DomainConstant.isCommonField(record.getJdbcFieldCode())){
            log.info("新增模型字段：{}，该字段为公共字段，不能添加", record.getJdbcFieldCode());
            throw new ResultException(Errors.OBJECT_RESERVED, "字段为公共字段");
        }
        record.setId(SnowflakeId.next());
        schemaFieldPersistence.save(record);
        return record;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(List<SchemaFieldDO> records) throws Exception {
        for (SchemaFieldDO record : records){
            if (StringUtils.isBlank(record.getJdbcFieldCode())){
                record.setJdbcFieldCode(StringToolkit.camel2Underline(record.getClassFieldCode()));
            }
            if (DomainConstant.isCommonField(record.getJdbcFieldCode())){
                log.info("批量新增模型字段：{}，该字段为公共字段，不能添加", record.getJdbcFieldCode());
                throw new ResultException(Errors.OBJECT_RESERVED, "字段" + record.getClassFieldCode() + "为公共字段");
            }
            record.setId(SnowflakeId.next());
        }
        schemaFieldPersistence.saveBatch(records);
    }

    @Override
    public void edit(EditFieldWrapper field) throws Exception {
        // 修改的时候，数据库的字段，不能随意变更
        // 当已经有记录时，对于修改的字段，数据类型不能进行随意变更
        // 1、varchar可以变更为clob, varchar的长度可以扩展，不能缩减
        // 2、int2-->int4-->int8
        if (!field.getOriginal().getClassFieldCode().equals(field.getRecord().getClassFieldCode())){
            // 字段发生了变更
            for (SchemaFieldDO schemaField : field.getSchemaFields()){
                if (!field.getOriginal().getId().equals(schemaField.getId())){
                    if (schemaField.getClassFieldCode().equals(field.getRecord().getClassFieldCode())){
                        log.info("修改模型字段：{} -> {}，该字段已经存在，不能修改", field.getOriginal().getClassFieldCode(), field.getRecord().getClassFieldCode());
                        throw new ResultException(Errors.OBJECT_HAS_PROPERTY, "模型字段已经存在");
                    }
                }
            }
            if (StringUtils.isBlank(field.getRecord().getJdbcFieldCode())){
                field.getRecord().setJdbcFieldCode(StringToolkit.camel2Underline(field.getRecord().getClassFieldCode()));
            }
            if (DomainConstant.isCommonField(field.getRecord().getJdbcFieldCode())){
                log.info("修改模型字段：{}，该字段为公共字段，不能变更", field.getRecord().getJdbcFieldCode());
                throw new ResultException(Errors.OBJECT_RESERVED, "字段" + field.getRecord().getClassFieldCode() + "为公共字段");
            }
        }
        MetaTable metaTable = metaRepository.loadTable(field.getSchemaDomain().getDomainTable());
        if (metaTable != null){
            // 该模型已经生成了数据表
            if (!field.getOriginal().getJdbcFieldCode().equals(field.getRecord().getJdbcFieldCode())){
                // 如果该字段已经被列为索引，则也不能变更字段名称
                if (!CollectionUtils.isEmpty(field.getSchemaIndexes())){
                    for (SchemaIndexDO schemaIndex : field.getSchemaIndexes()){
                        if (schemaIndex.isContainColumn(field.getOriginal().getJdbcFieldCode())){
                            log.info("修改模型字段：{} 的字段：{}，该字段已经被索引，不能修改字段名称", field.getSchemaDomain().getDomainCode(), field.getRecord().getJdbcFieldCode());
                            throw new ResultException(Errors.OBJECT_REFERENCED, "模型字段已经被索引");
                        }
                    }
                }
            }
            if (field.getDomainRecordCount() > 0){
                // 该模型已经有记录了，不能随意变更数据字段
                if (field.getOriginal().getJdbcFieldType().equals(field.getRecord().getJdbcFieldType())){
                    // 数据库字段发生改变了
                    if (!SchemaConstant.Jdbc.isTypeAllowAlter(field.getOriginal().getJdbcFieldType(), field.getRecord().getJdbcFieldType())){
                        // 不支持的字段变更
                        log.info("修改模型字段：{}，不支持从数据类型：{} 变更为：{}", field.getOriginal().getJdbcFieldCode(), field.getOriginal().getJdbcFieldType(), field.getRecord().getJdbcFieldType());
                        throw new ResultException(Errors.OBJECT_TYPE_CONVERT_UNSUPPORTED, "不支持模型字段的数据类型变更");
                    }
                }
            }
        }
    }

    @Override
    public void delete(DeleteFieldWrapper field) throws Exception {
        // 不能删除公共字段
        if (DomainConstant.isCommonField(field.getRecord().getJdbcFieldCode())){
            log.info("删除模型字段：{}，该字段为公共字段，不能删除", field.getRecord().getJdbcFieldCode());
            throw new ResultException(Errors.OBJECT_RESERVED, "不能删除公共字段");
        }
        // 判断该字段是否被索引了
        if (!CollectionUtils.isEmpty(field.getSchemaIndexes())){
            for (SchemaIndexDO schemaIndex : field.getSchemaIndexes()){
                if (schemaIndex.isContainColumn(field.getRecord().getJdbcFieldCode())){
                    log.info("删除模型：{} 的字段：{}，该字段已经被索引，不能删除", field.getSchemaDomain().getDomainCode(), field.getRecord().getJdbcFieldCode());
                    throw new ResultException(Errors.OBJECT_REFERENCED, "模型字段已经被索引");
                }
            }
        }
        // 如果该模型已经建表，则不直接删除数据表中的字段
        schemaFieldPersistence.removeById(field.getRecord().getId());
    }

    @Override
    public ListResultWrapper<SchemaFieldDO> query(SchemaFieldQueryInput input, Integer pageNo, Integer pageSize) {
        QueryWrapper<SchemaFieldDO> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(input.getClassDataType() != null, SchemaFieldDO::getClassDataType, input.getClassDataType())
                .eq(input.getJdbcFieldType() != null, SchemaFieldDO::getJdbcFieldType, input.getJdbcFieldType())
                .eq(input.getJdbcNullable() != null, SchemaFieldDO::getJdbcNullable, input.getJdbcNullable())
                .like(!StringUtils.isBlank(input.getDomainCode()), SchemaFieldDO::getDomainCode, input.getDomainCode())
                .and(!StringUtils.isBlank(input.getKey()), w -> w.like(SchemaFieldDO::getClassFieldCode, input.getKey())
                        .or().like(SchemaFieldDO::getJdbcFieldCode, input.getKey()))
                .orderByAsc(SchemaFieldDO::getDomainCode)
                .orderByAsc(SchemaFieldDO::getPriority)
                .orderByAsc(SchemaFieldDO::getClassFieldCode);
        if (pageNo < 1 || pageSize < 1){
            // 不分页
            List<SchemaFieldDO> records = schemaFieldPersistence.list(wrapper);
            return new ListResultWrapper<>(records);
        }
        Page<SchemaFieldDO> page = new Page<>(pageNo, pageSize);
        Page<SchemaFieldDO> records = schemaFieldPersistence.page(page, wrapper);
        ListResultWrapper<SchemaFieldDO> output = new ListResultWrapper<>(records.getRecords());
        output.setTotal((int)records.getTotal());
        return output;
    }
}
